package com.libleveldb

import com.libleveldb.exception.LevelDBClosedException
import com.libleveldb.exception.LevelDBException
import com.libleveldb.exception.LevelDBSnapshotOwnershipException
import com.libleveldb.implementation.NativeLevelDB
import com.libleveldb.implementation.mock.MockLevelDB
import java.io.Closeable
import kotlin.math.abs

abstract class LevelDB : Closeable {
    /**
     * Closes this LevelDB instance. Database is usually not usable after a call to this method.
     */
    abstract override fun close()

    /**
     * Writes the key-value pair in the database.
     *
     * @param key   non-null, if null throws [java.lang.IllegalArgumentException]
     * @param value non-null, if null same as [.del]
     * @param sync  whether this write will be forced to disk
     * @throws LevelDBException throw LevelDBException
     */
    @Throws(LevelDBException::class)
    abstract fun put(key: ByteArray?, value: ByteArray?, sync: Boolean)

    /**
     * Asynchronous [.put].
     *
     * @param key   non-null, if null throws [java.lang.IllegalArgumentException]
     * @param value non-null, if null same as [.del]
     * @throws LevelDBException throw LevelDBException
     */
    @Throws(LevelDBException::class)
    fun put(key: ByteArray?, value: ByteArray?) {
        put(key, value, false)
    }

    /**
     * Writes a [com.libleveldb.WriteBatch] to the database.
     *
     * @param writeBatch non-null, if null throws [java.lang.IllegalArgumentException]
     * @param sync       whether this write will be forced to disk
     * @throws LevelDBException throw LevelDBException
     */
    @Throws(LevelDBException::class)
    abstract fun write(writeBatch: WriteBatch?, sync: Boolean)

    /**
     * Asynchronous [.write].
     *
     * @param writeBatch non-null, if null throws [java.lang.IllegalArgumentException]
     * @throws LevelDBException throw LevelDBException
     */
    @Throws(LevelDBException::class)
    fun write(writeBatch: WriteBatch?) {
        write(writeBatch, false)
    }

    /**
     * Retrieves key from the database, possibly from a snapshot state.
     *
     * @param key      non-null, if null throws [java.lang.IllegalArgumentException]
     * @param snapshot the snapshot from which to read the entry, may be null
     * @return data for the key, or null
     * @throws LevelDBException throw LevelDBException
     */
    @Throws(LevelDBSnapshotOwnershipException::class, LevelDBException::class)
    abstract operator fun get(key: ByteArray?, snapshot: Snapshot?): ByteArray?

    /**
     * Retrieves key from the database with an implicit snapshot.
     *
     * @see .get
     */
    @Throws(LevelDBException::class)
    operator fun get(key: ByteArray?): ByteArray? {
        return get(key, null)
    }

    /**
     * Deletes key from database, if it exists.
     *
     * @param key  non-null, if null throws [java.lang.IllegalArgumentException]
     * @param sync whether this write will be forced to disk
     * @throws LevelDBException throw LevelDBException
     */
    @Throws(LevelDBException::class)
    abstract fun del(key: ByteArray?, sync: Boolean)

    /**
     * Asynchronous [.del].
     *
     * @param key non-null, if null throws [java.lang.IllegalArgumentException]
     * @throws LevelDBException throw LevelDBException
     */
    @Throws(LevelDBException::class)
    fun del(key: ByteArray?) {
        del(key, false)
    }

    /**
     * Raw form of [.getProperty].
     *
     *
     * Retrieves the LevelDB property entry specified with key.
     *
     * @param key non-null, if null throws [java.lang.IllegalArgumentException]
     * @return property bytes
     * @throws LevelDBClosedException throw LevelDBClosedException
     */
    @Throws(LevelDBClosedException::class)
    abstract fun getPropertyBytes(key: ByteArray?): ByteArray?

    /**
     * Convenience function.
     *
     * @see com.libleveldb.LevelDB.getPropertyBytes
     */
    @Throws(LevelDBClosedException::class)
    fun getProperty(key: ByteArray?): String? {
        val value = getPropertyBytes(key) ?: return null
        return String(value)
    }

    /**
     * Convenience function.
     *
     * @see com.libleveldb.LevelDB.getPropertyBytes
     */
    @Throws(LevelDBClosedException::class)
    fun getProperty(key: String?): String? {
        return getProperty(key?.toByteArray())
    }

    /**
     * Creates a new [com.libleveldb.Iterator] for this database.
     *
     *
     * Data seen by the iterator will be consistent (like a snapshot). Closing the iterator is a must.
     * The database implementation will not close iterators automatically when closed, which may
     * result in memory leaks.
     *
     * @param fillCache whether to fill the internal cache while iterating over the database
     * @param snapshot  the snapshot from which to read the entries, may be null
     * @return new iterator
     * @throws LevelDBClosedException throw LevelDBClosedException
     */
    @Throws(LevelDBSnapshotOwnershipException::class, LevelDBClosedException::class)
    abstract fun iterator(fillCache: Boolean, snapshot: Snapshot?): Iterator?

    /**
     * Iterate over the database with an implicit snapshot created at the time of creation
     * of the iterator.
     *
     * @param fillCache whether to fill the internal cache while iterating over the database
     * @return a new iterator
     * @throws LevelDBClosedException throw LevelDBClosedException
     */
    @Throws(LevelDBClosedException::class)
    open fun iterator(fillCache: Boolean): Iterator? {
        return iterator(fillCache, null)
    }

    /**
     * Iterate over the entries from snapshot while filling the cache.
     *
     * @param snapshot the snapshot from which to read the entries, may be null
     * @return a new iterator
     * @throws LevelDBSnapshotOwnershipException throw LevelDBSnapshotOwnershipException
     * @throws LevelDBClosedException            throw LevelDBClosedException
     */
    @Throws(LevelDBSnapshotOwnershipException::class, LevelDBClosedException::class)
    fun iterator(snapshot: Snapshot?): Iterator? {
        return iterator(true, snapshot)
    }

    /**
     * Creates a new iterator that fills the cache.
     *
     * @return a new iterator
     * @throws com.libleveldb.exception.LevelDBClosedException throw LevelDBClosedException
     * @see .iterator
     */
    @Throws(LevelDBClosedException::class)
    operator fun iterator(): Iterator? {
        return iterator(true)
    }

    /**
     * The path of this LevelDB. Usually a filesystem path, but may be something else
     * (eg: [com.libleveldb.implementation.mock.MockLevelDB.path].
     *
     * @return the path of this database, may be null
     */
    abstract var path: String?
        protected set

    /**
     * Atomically check if this database has been closed.
     *
     * @return whether it's been closed
     */
    abstract val isClosed: Boolean

    /**
     * Obtains a new snapshot of this database's data.
     *
     *
     * Make sure you call [.releaseSnapshot] when you are done with it.
     *
     * @return a new snapshot
     */
    @Throws(LevelDBClosedException::class)
    abstract fun obtainSnapshot(): Snapshot?

    /**
     * Releases a previously obtained snapshot. It is not an error to release a snapshot
     * multiple times.
     *
     *
     * If this database does not own the snapshot, a [com.libleveldb.exception.LevelDBSnapshotOwnershipException]
     * will be thrown at runtime.
     *
     * @param snapshot the snapshot to release, if null throws a [java.lang.IllegalArgumentException]
     */
    @Throws(LevelDBSnapshotOwnershipException::class, LevelDBClosedException::class)
    abstract fun releaseSnapshot(snapshot: Snapshot?)

    /**
     * Specifies a configuration to open the database with.
     */
    class Configuration {
        private var createIfMissing = true
        private var cacheSize = 0
        private var blockSize = 0
        private var writeBufferSize = 0
        fun createIfMissing(): Boolean {
            return createIfMissing
        }

        fun createIfMissing(createIfMissing: Boolean): Configuration {
            this.createIfMissing = createIfMissing
            return this
        }

        fun cacheSize(): Int {
            return cacheSize
        }

        fun cacheSize(cacheSize: Int): Configuration {
            this.cacheSize = abs(cacheSize)
            return this
        }

        fun blockSize(): Int {
            return blockSize
        }

        fun blockSize(blockSize: Int): Configuration {
            this.blockSize = abs(blockSize)
            return this
        }

        fun writeBufferSize(): Int {
            return writeBufferSize
        }

        fun writeBufferSize(writeBufferSize: Int): Configuration {
            this.writeBufferSize = writeBufferSize
            return this
        }
    }

    companion object {
        /**
         * Convenience for [.open]
         *
         * @param path the path to the database
         * @return a new [com.libleveldb.implementation.NativeLevelDB] instance
         * @throws LevelDBException throw LevelDBException
         */
        @JvmOverloads
        @Throws(LevelDBException::class)
        fun open(path: String, configuration: Configuration? = configure()): LevelDB {
            return NativeLevelDB(path, configuration)
        }

        /**
         * Use this method to obtain a [com.libleveldb.LevelDB.Configuration] object.
         *
         * @return a new [com.libleveldb.LevelDB.Configuration] object
         */
        @JvmStatic
        fun configure(): Configuration {
            return Configuration()
        }

        /**
         * Creates a new [com.libleveldb.implementation.mock.MockLevelDB] useful in
         * testing in non-Android environments such as Robolectric. It does not access the filesystem,
         * and is in-memory only.
         *
         * @return a new [com.libleveldb.implementation.mock.MockLevelDB]
         */
        fun mock(): LevelDB {
            return MockLevelDB()
        }

        /**
         * Destroys the contents of a LevelDB database.
         *
         * @param path the path to the database
         * @throws com.libleveldb.exception.LevelDBException throw LevelDBException
         * @see com.libleveldb.implementation.NativeLevelDB.destroy
         */
        @JvmStatic
        @Throws(LevelDBException::class)
        fun destroy(path: String) {
            NativeLevelDB.destroy(path)
        }

        /**
         * If a DB cannot be opened, you may attempt to call this method to resurrect as much of the contents of the
         * database as possible. Some data may be lost, so be careful when calling this function on a database that contains
         * important information.
         *
         * @param path the path to the database
         * @throws com.libleveldb.exception.LevelDBException throw LevelDBException
         * @see com.libleveldb.implementation.NativeLevelDB.repair
         */
        @JvmStatic
        @Throws(LevelDBException::class)
        fun repair(path: String) {
            NativeLevelDB.repair(path)
        }
    }
}